#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/slab.h> 

#define CHRDEV_MAJOR 200
#define CHRDEV_NAME "leaktest"
#define DEVICE_NUMBER 1

dev_t devno;
struct cdev *leaktest_dev;
struct class *class;
struct device *device;

struct leakbuf {
    char *ptr[1024 * 20];
    int index;
} leakbuf = {0};


static int leaktest_open(struct inode *inode, struct file *filp)
{
    memset(&leakbuf, 0, sizeof(leakbuf));
    printk("chartest_open\n");
    return 0;
}

static long leaktest_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
    char *buf;
    switch (cmd) {
        case 1:
            if (leakbuf.index >= ARRAY_SIZE(leakbuf.ptr)) {
                printk("you got the limit of leak, buf index: %d\n", leakbuf.index);
                return -ENOMEM;
            }
            buf = (char *)kzalloc(arg, GFP_KERNEL);
            if (buf == NULL) {
                printk("kmalloc failed! size=%ld\n", arg);
                return -ENOMEM;
            }
            leakbuf.ptr[leakbuf.index] = buf;
            leakbuf.index++;
            printk("kmalloc: %p size=%ld index=%d\n", buf, arg, leakbuf.index);
            break;

        case -1:
            printk("kfree\n");
            break;

        default:
            return -ENODEV;
            break;
    }

    return 0;
}

static int leaktest_release(struct inode *inode, struct file *filp)
{
    int i;
    for (i = 0; i < leakbuf.index; i++) {
        kfree(leakbuf.ptr[i]);
        printk("kfree: %p\n", leakbuf.ptr[i]);
    }
    leakbuf.index = 0;

    printk("chartest_release\n");
    return 0;
}

static struct file_operations leaktest_fops = {
    .owner = THIS_MODULE,
    .open = leaktest_open,
    .unlocked_ioctl = leaktest_ioctl,
    .release = leaktest_release,
};

static int __init leaktest_init(void)
{
    int ret = 0;
    if (CHRDEV_MAJOR) {
        devno = MKDEV(CHRDEV_MAJOR, 0);
        ret = register_chrdev_region(devno, DEVICE_NUMBER, CHRDEV_NAME);
        if (ret < 0) {
            printk("register_chrdev_region failed\n");
            return 0;
        }
    } else {
        ret = alloc_chrdev_region(&devno, 0, DEVICE_NUMBER, CHRDEV_NAME);
        if (ret < 0) {
            printk("alloc_chrdev_region failed\n");
            return 0;
        }
    }

    leaktest_dev = cdev_alloc();
    if (leaktest_dev == NULL) {
        printk(KERN_WARNING "cdev_alloc\n");
    }

    cdev_init(leaktest_dev, &leaktest_fops);
    leaktest_dev->owner = THIS_MODULE;
    cdev_add(leaktest_dev, devno, 1);
    class = class_create(THIS_MODULE, CHRDEV_NAME);
    device = device_create(class, NULL, devno, NULL, CHRDEV_NAME);

    printk(KERN_WARNING "leaktest_init\n");
    return 0;
}

static void __exit leaktest_exit(void)
{
    device_destroy(class, devno);
    class_destroy(class);
    cdev_del(leaktest_dev);
    unregister_chrdev_region(devno, DEVICE_NUMBER);

    printk(KERN_WARNING "leaktest_exit\n");
}

module_init(leaktest_init);
module_exit(leaktest_exit);
MODULE_LICENSE("GPL");
